﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Web;
using Cookbook.Common;

namespace Cookbook.Services.Core.ExceptionHandling
{
    /// <summary>
    /// Provides a thread-specific context for collecting multiple rule violations along a path of executing of a <see cref="ICommand"/>.
    /// </summary>
    public class ValidationContext : IDisposable, IViolationCollection
    {
        #region Private Definitions

        private static readonly string contextKey = typeof(ValidationContext).FullName;
        private readonly List<Violation> violations = new List<Violation>();

        #endregion

        /// <summary>
        /// Gets or sets the violation collection associated with the current thread. 
        /// </summary>
        /// <remarks>
        /// Even if there is no specific collection available, you can safely refer to this property. 
        /// </remarks>
        public static IViolationCollection Current
        {
            get
            {
                IViolationCollection collection;

                // See http://piers7.blogspot.com/2005/11/threadstatic-callcontext-and_02.html
                if (HttpContext.Current != null)
                {
                    collection = HttpContext.Current.Items[contextKey] as IViolationCollection;
                }
                else
                {
                    collection = CallContext.GetData(contextKey) as IViolationCollection;
                }

                return collection ?? new ThrowingViolationCollection();
            }
            set
            {
                if (HttpContext.Current != null)
                {
                    HttpContext.Current.Items[contextKey] = value;
                }
                else
                {
                    CallContext.SetData(contextKey, value);
                }
            }
        }

        /// <summary>
        /// Creates a new collecting violation context on the current thread that continues to exist until <see cref="Dispose"/> is called.
        /// </summary>
        public static ValidationContext CreateOrReuse()
        {
            var violationContext = Current as ValidationContext;
            if (violationContext == null)
            {
                violationContext = new ValidationContext();
                Current = violationContext;
            }

            return violationContext;
        }

        /// <summary>
        /// Removes the collecting violation collection from the current thread.
        /// </summary>
        public void Dispose()
        {
            Current = null;
        }

        public void Add(Rule violatedRule)
        {
            violations.Add(new Violation(violatedRule));
        }

        public void Add(Violation violation)
        {
            violations.Add(violation);
        }

        /// <summary>
        /// Throws a <see cref="RulesViolationException"/> if the violation collection contains any rule violations.
        /// </summary>
        public void ThrowIfAny()
        {
            if (violations.Any())
            {
                throw new RuleViolationException(violations);
            }
        }
    }
}